#include "app/text.h"
#include "app/piddemo.h"
#include "hardware/lcd.h"
#include "hardware/lcd_init.h"
#include "hardware/motor.h"
#include "hardware/encoder.h"
#include "mid/pid.h"
#include "mid/printf.h"
#include "mid/ui.h"
#include "mid/widgets/button.h"
#include "mid/widgets/label.h"
#include "mid/widgets/window.h"
#include "mid/utils.h"

#include <string.h>
#include <stdlib.h> // 随机数

typedef struct FixedSpeedWindow_t
{
    Label lblDir;
    Label lblP;
    Label lblI;
    Label lblD;
    Button btnP;
    Button btnI;
    Button btnD;
    Label lblSpeed;
    Label lblSpeedValue;
    Button btnTarget;
} FixedSpeedWindow;

static FixedSpeedWindow gFixedSpeedWin;
extern PID gPID;
extern char gTuning;
extern char gPText[8];
extern char gIText[8];
extern char gDText[8];
extern char gSpeedText[8];
extern char gTargetText[16];
extern int gSelected;
char gSkipValue = 0;

void RenderFixedSpeedWindow();
void UpdateFixedSpeedWindow();
int ProcessInputFixedSpeedWindow(eButtonID btnID, eButtonEvent btnEvent);
void UpdateSpeedText();
void UpdateSpeedButtonState();

Window* GetFixedSpeedWindow()
{
    static Window window;

    window.Render = RenderFixedSpeedWindow;
    window.Update = UpdateFixedSpeedWindow;
    window.ProcessInput = ProcessInputFixedSpeedWindow;

    gFixedSpeedWin.lblDir = BuildLabel(LCD_W / 2, CURVE_SY - FONT_SIZE - 20, LABEL_IDLE, WHITE, BLACK);

    int lblY = LCD_H / 2 + FONT_SIZE;
    int pX = LCD_W / 3 - FONT_SIZE * 2;
    int iX = LCD_W / 2;
    int dX = LCD_W * 2 / 3 + FONT_SIZE * 2;
    gFixedSpeedWin.lblP = BuildLabel(pX, lblY, LABEL_P, WHITE, BLACK);
    gFixedSpeedWin.lblI = BuildLabel(iX, lblY, LABEL_I, WHITE, BLACK);
    gFixedSpeedWin.lblD = BuildLabel(dX, lblY, LABEL_D, WHITE, BLACK);

    int btnW = 60;
    int btnH = 20;
    int deltaY = (FONT_SIZE + btnH) / 2 + 3; // gap: 3
    int btnY = lblY + deltaY;
    gFixedSpeedWin.btnP = BuildButton(pX, btnY, btnW, btnH, gPText, WHITE, BLUE);
    gFixedSpeedWin.btnI = BuildButton(iX, btnY, btnW, btnH, gIText, WHITE, BLUE);
    gFixedSpeedWin.btnD = BuildButton(dX, btnY, btnW, btnH, gDText, WHITE, BLUE);

    int lbl2Y = btnY + btnH / 2 + FONT_SIZE + 3; // gap: 8
    int speedX = LCD_W / 2 - FONT_SIZE / 2;
    int speedVX = speedX + StrWidth(LABEL_SPEED) / 2 + StrWidth("999") / 2 + 1; // gap: 1
    gFixedSpeedWin.lblSpeed = BuildLabel(speedX, lbl2Y, LABEL_SPEED, WHITE, BLACK);
    gFixedSpeedWin.lblSpeedValue = BuildLabel(speedVX + StrWidth("999") / 2, lbl2Y, gSpeedText, WHITE, BLACK);

    // target
    int tx = LCD_W / 2;//speedX + StrWidth(LABEL_SPEED) / 2 + StrWidth("999") / 2 + 30; // gap: 30
    gFixedSpeedWin.btnTarget = BuildButton(tx, lbl2Y + FONT_SIZE + btnH / 2, 100, btnH, gTargetText, WHITE, BLACK);

    gSelected = 0;
    InitPID(&gPID, 88, 6, 13, MAX_SPEED, MAX_SPEED, 88, MAX_SPEED_TARGET);
    UpdateSpeedButtonState();
    UpdatePIDText(&gPID, ePID_ALL);
    UpdateSpeedText();
    return &window;
}

void RenderSpeedUI()
{
    // DrawLabel(&gFixedSpeedWin.lblDir);
    DrawLabel(&gFixedSpeedWin.lblP);
    DrawLabel(&gFixedSpeedWin.lblI);
    DrawLabel(&gFixedSpeedWin.lblD);

    RenderButton(&gFixedSpeedWin.btnP);
    RenderButton(&gFixedSpeedWin.btnI);
    RenderButton(&gFixedSpeedWin.btnD);
    RenderButton(&gFixedSpeedWin.btnTarget);

    DrawLabel(&gFixedSpeedWin.lblSpeed);
    DrawLabel(&gFixedSpeedWin.lblSpeedValue);
}

void RenderFixedSpeedWindow()
{
    RenderSpeedUI();

    if (gTuning)
    {
        if (gSkipValue)
        {
            gSkipValue = 0;
            return;
        }
        int current = NormalizeY(GetEncoderValue() + MAX_SPEED_TARGET, 2 * MAX_SPEED_TARGET);
        int x = DrawCurve(CURVE_SX, CURVE_SY, CURVE_WIDTH, CURVE_HEIGHT, GREEN, BLACK, current);

        int target = NormalizeY(gPID.target + MAX_SPEED_TARGET, 2 * MAX_SPEED_TARGET);
        LCD_DrawPoint(x, CURVE_SY + CURVE_HEIGHT - target, RED);
    }
    else
        StopMotor();
}

void UpdateSpeedText()
{
    memset(gSpeedText, 0, sizeof(gSpeedText));
    sprintf(gSpeedText, "%3d", GetEncoderValue());
    printf("speed=%d\n", GetEncoderValue());
}

void UpdateFixedSpeedWindow()
{
    if (gTuning)
        MotorSpeedControl(&gPID);
    // update lblSpeedValue
    UpdateSpeedText();
    gFixedSpeedWin.lblSpeedValue.x = gFixedSpeedWin.lblSpeed.x + StrWidth(LABEL_SPEED) / 2 + StrWidth(gSpeedText) / 2 + 1;
}

void UpdateSpeedButtonState()
{
    Select(&gFixedSpeedWin.btnP, 0 == gSelected);
    Select(&gFixedSpeedWin.btnI, 1 == gSelected);
    Select(&gFixedSpeedWin.btnD, 2 == gSelected);
    Select(&gFixedSpeedWin.btnTarget, 3 == gSelected);
}

int ProcessInputFixedSpeedWindow(eButtonID btnID, eButtonEvent btnEvent)
{
    if (FLEX_BTN_PRESS_DOUBLE_CLICK == btnEvent && USER_BUTTON_MID == btnID)
    {
        gTuning = 0;
        StopMotor();
        GetUIMgr()->Back();
        return 1;
    }

    if (FLEX_BTN_PRESS_CLICK != btnEvent &&
        FLEX_BTN_PRESS_LONG_HOLD != btnEvent
    )
        return 0;

    if (FLEX_BTN_PRESS_LONG_HOLD == btnEvent)
    {
        // holding 开始到结束前，主循环不会执行
        if (btnID == USER_BUTTON_UP)
            UpdatePIDTargetValue(&gPID, 3);
        else if (btnID == USER_BUTTON_DOWN)
            UpdatePIDTargetValue(&gPID, -3);
        else
            return 0;
        RenderSpeedUI();
        return 1;
    }

    switch (btnID)
    {
    case USER_BUTTON_LEFT:
        gSelected = (gSelected - 1) % ePID_BTN_CNT;
        if (gSelected < 0)
            gSelected += ePID_BTN_CNT;
        UpdateSpeedButtonState();
        break;
    
    case USER_BUTTON_RIGHT:
        gSelected = (gSelected + 1) % ePID_BTN_CNT;
        UpdateSpeedButtonState();
        break;

    case USER_BUTTON_UP:
        UpdatePIDTargetValue(&gPID, 1);
        break;

    case USER_BUTTON_DOWN:
        UpdatePIDTargetValue(&gPID, -1);
        break;

    case USER_BUTTON_MID:
        gTuning = !gTuning;
        if (gTuning)
        {
            gSkipValue = 0;
            ClearCurve(CURVE_SX, CURVE_SY, CURVE_WIDTH, CURVE_HEIGHT, BLACK);
            ResetCurveState();
            InitPID(&gPID, gPID.kP, gPID.kI, gPID.kD, MAX_SPEED, MAX_SPEED, gPID.target, MAX_SPEED_TARGET);
            DrawBoundingBox(CURVE_SX, CURVE_SY, CURVE_WIDTH, CURVE_HEIGHT, YELLOW);
            DrawZeroMark(CURVE_SX, CURVE_SY, CURVE_WIDTH, CURVE_HEIGHT, YELLOW);
        }
        else
        {
            StopMotor();
        }
        break;

    default:
        return 0;
    }
    gSkipValue = 1;

    RenderSpeedUI();

    return 1;
}